﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Win32;
using QTPlugin;
using Kawanon.QtHoney;
using System.Windows.Forms;
using Kawanon.Usability.Visualization;
using Kawanon.Localization;

namespace Kawanon.Usability
{
	public static class GestureSettings
	{
		private enum Categories
		{
			Custom = 0,
			General,
			Navigation,
			Tab,
			Window,
			Settings,
			Sort,
			File,
			Scroll,
			Clipboard,
		}

		private static List<GestureExpression> s_exprs = new List<GestureExpression>();
		private static Dictionary<Action, GestureDescription> s_descriptions = new Dictionary<Action, GestureDescription>();

		static GestureSettings()
		{
			var empty = GestureExpression.EmptyTrails;
			string general = GetCategory(Categories.General);
			string navigation = GetCategory(Categories.Navigation);
			string tab = GetCategory(Categories.Tab);
			string window = GetCategory(Categories.Window);
			string settings = GetCategory(Categories.Settings);
			string tabSort = GetCategory(Categories.Sort);
			string file = GetCategory(Categories.File);
			string scroll = GetCategory(Categories.Scroll);
			string clipboard = GetCategory(Categories.Clipboard);

			AddDefault(new GestureExpression("L", Action.From(Commands.GoBack, 1)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("R", Action.From(Commands.GoForward, 1)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("DU", Action.From(Commands.GoUpOneLevel)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("UD", Action.From(Commands.RefreshBrowser)), new GestureDescription(general, false));
			AddDefault(new GestureExpression("D", Action.From(Commands.CloseCurrentTab)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseLeft)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseRight)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseAllButCurrent)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.CloseWindow)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.UndoClose)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ToggleTopMost)), new GestureDescription(general, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.OpenTabBarOptionDialog)), new GestureDescription(settings, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.OpenButtonBarOptionDialog)), new GestureDescription(settings, false));
			AddDefault(new GestureExpression("UR", new Actions.NextTab()), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression("UL", new Actions.PreviousTab()), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression(empty, new Actions.IsolateTab()), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, new Actions.MergeWindows()), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowGestureSettings()), new GestureDescription(settings, false));
			AddDefault(new GestureExpression(empty, new Actions.NewTab()), new GestureDescription(tab, true));
			AddDefault(new GestureExpression(empty, new Actions.NewWindow()), new GestureDescription(window, true));
			//AddDefault(new GestureExpression(empty, new Actions.SendKeys("コピー", "^C")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.SendKeys("切り取り", "^X")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("copy")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("cut")), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.FileOperation("paste")), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_MAXIMIZE)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_MINIMIZE)), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowWindow(Actions.ShowWindow.SW_RESTORE)), new GestureDescription(window, false));
			//AddDefault(new GestureExpression(empty, new Actions.CreateProcess("","")), new GestureDescription(generic, true));
			//AddDefault(new GestureExpression(empty,
			//    new Actions.ShellBrowserAccelerator(Actions.ShellBrowserAccelerator.Accelerators.Copy)),
			//    new GestureDescription(generic, true));
			AddDefault(new GestureExpression(empty, Action.From(Commands.BrowseFolder)), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.MD5)), new GestureDescription(general, true));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByName)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByPath)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsByActv)), new GestureDescription(tabSort, false));
			AddDefault(new GestureExpression(empty, Action.From(Commands.ReorderTabsRevers)), new GestureDescription(tabSort, false));

			AddDefault(new GestureExpression(empty, new Actions.MinimizeToTray()), new GestureDescription(window, false));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.Cut)), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.Copy)), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.Paste)), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.Undo)), new GestureDescription(file, true));

			AddDefault(new GestureExpression(empty, new Actions.ShowGroupMenu(false)), new GestureDescription(general, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowGroupMenu(true)), new GestureDescription(general, false));
			AddDefault(new GestureExpression(empty, new Actions.LockUnlockTab()), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, new Actions.Scroll(Actions.Scroll.Direction.Top)), new GestureDescription(scroll, false));
			AddDefault(new GestureExpression(empty, new Actions.Scroll(Actions.Scroll.Direction.Bottom)), new GestureDescription(scroll, false));
			AddDefault(new GestureExpression(empty, new Actions.Scroll(Actions.Scroll.Direction.Left)), new GestureDescription(scroll, false));
			AddDefault(new GestureExpression(empty, new Actions.Scroll(Actions.Scroll.Direction.Right)), new GestureDescription(scroll, false));

			AddDefault(new GestureExpression(empty, new Actions.CopyPath(Actions.CopyPath.Source.Selection)), new GestureDescription(clipboard, true));
			AddDefault(new GestureExpression(empty, new Actions.CopyPath(Actions.CopyPath.Source.SelectedTab)), new GestureDescription(clipboard, false));
			AddDefault(new GestureExpression(empty, new Actions.CopyPath(Actions.CopyPath.Source.Tabs)), new GestureDescription(clipboard, false));

			AddDefault(new GestureExpression(empty, new Actions.CloseAllUnlocked()), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, new Actions.ShowQtHoneySettings()), new GestureDescription(settings, false));
			AddDefault(new GestureExpression(empty, new Actions.GoFirstOrLast(true)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression(empty, new Actions.GoFirstOrLast(false)), new GestureDescription(navigation, false));
			AddDefault(new GestureExpression(empty, new Actions.LastActiveTab()), new GestureDescription(tab, false));

			AddDefault(new GestureExpression(empty, new Actions.CloneTab()), new GestureDescription(tab, false));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.Delete)), new GestureDescription(file, true));
			//AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.CompletelyDelete)), new GestureDescription(file, true));
			AddDefault(new GestureExpression(empty, new Actions.SendInput(Actions.SendInput.ShortcutKeys.SelectAll)), new GestureDescription(file, true));
		}

		private static void AddDefault(GestureExpression expr, GestureDescription desc)
		{
			s_exprs.Add(expr);
			s_descriptions.Add(expr.Action, desc);
		}

		public static GestureDescription GetDescription(Action action)
		{
			GestureDescription desc = null;
			s_descriptions.TryGetValue(action, out desc);
			return desc;
		}

		public static bool IsAcceptTarget(Action action)
		{
			if (action is CustomAction)
			{
				return ((CustomAction)action).IsAcceptTarget;
			}
			else
			{
				GestureDescription desc = GetDescription(action);
				if (desc == null)
				{
					Dbg.Abort("desc == null");
				}
				return desc.IsAcceptTarget;
			}
		}

		private static string GetCategory(Categories category)
		{
			return StringResource.Localize("GestureSettings_Category", (int)category, false);
		}

		public static string GetCategory(Action action)
		{
			if (action is CustomAction)
			{
				return GetCategory(Categories.Custom);
			}
			else
			{
				GestureDescription desc = GetDescription(action);
				if (desc == null)
				{
					Dbg.Abort("desc == null");
				}
				return desc.Category;
			}
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, Action action)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.Action.Equals(action))
				{
					return expr;
				}
			}
			return null;
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, int[] trails)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.TrailsEquals(trails))
				{
					return expr;
				}
			}
			return null;
		}

		public static GestureExpression FindExpression(IEnumerable<GestureExpression> list, int[] trails, Keys button)
		{
			foreach (GestureExpression expr in list)
			{
				if (expr.TrailsEquals(trails) && expr.Button == button)
				{
					return expr;
				}
			}
			return null;
		}

		public static IList<GestureExpression> GetDefaultExpressions()
		{
			return s_exprs.AsReadOnly();
		}

		public static IList<GestureExpression> GetAvailableExpressions(IList<GestureExpression> src)
		{
			List<GestureExpression> list = new List<GestureExpression>(src);
			list.RemoveAll((Predicate<GestureExpression>)
				delegate(GestureExpression expr)
				{
					if (expr.Trails == null)
					{
						return true;
					}
					else if (expr.Trails.Length == 0)
					{
						return true;
					}
					else if (expr.Action == null)
					{
						return true;
					}
					return false;
				});
			return list;
		}

		public static List<GestureExpression> OverrideExpressions(IEnumerable<GestureExpression> list, IEnumerable<GestureExpression> src)
		{
			List<GestureExpression> dest = new List<GestureExpression>(list);
			foreach (GestureExpression expr in src)
			{
				GestureExpression oldExpr = FindExpression(dest, expr.Action);
				if (oldExpr != null)
				{
					//Utility.Swap(dest, oldExpr, expr);
					int index = dest.IndexOf(oldExpr);
					dest.RemoveAt(index);

					// expr 自身を除く軌跡を調べる
					if (expr.Trails.Length > 0)
					{
						// プリセットの変更で衝突が起きてないか？
						GestureExpression dup = FindExpression(dest, expr.Trails, expr.Button);
						if (dup != null)
						{
							Utility.Swap(dest, dup,
								new GestureExpression(GestureExpression.EmptyTrails, dup));
						}
					}

					dest.Insert(index, expr);
				}
				else
				{
					// カスタムアクションのはず
					if (!(expr.Action is CustomAction))
					{
						throw new Exception("Expr is not Custom Action");
					}
					dest.Add(expr);
				}
			}
			return dest;
		}

		public static IList<GestureExpression> LoadAllExpressions()
		{
			List<GestureExpression> src = new List<GestureExpression>();
			LoadExpressions(src);
			IList<GestureExpression> list = GestureSettings.OverrideExpressions(
				GestureSettings.GetDefaultExpressions(), src);
			return list;
		}

		public static IList<GestureExpression> LoadAvailableExpressions()
		{
			return GestureSettings.GetAvailableExpressions(LoadAllExpressions());
		}

		public static bool LoadExpressions(ICollection<GestureExpression> list)
		{
			Stream stream = Persistence.CreateValueStream("Expressions");
			if (stream == null)
			{
				return false;
			}
			LoadExpressions(list, stream);
			return true;
		}

		public static void LoadExpressions(ICollection<GestureExpression> list, Stream istream)
		{
			// 未実装のアクションは無視する
			IEnumerable<GestureExpression> e = 
				Persistence.UnpackArchive<GestureExpression>(istream, false);
			foreach (GestureExpression expr in e)
			{
				list.Add(expr);
			}
		}

		//private static object[] ArrayFromExpressions(ICollection<GestureExpression> list)
		//{
		//    ExpressionArchiveHeader header = new ExpressionArchiveHeader();
		//    header.Version = ARCHIVE_VERSION_EXPRESSIONS;
		//    header.ObjectCount = list.Count;

		//    object[] a = new object[list.Count + 1];
		//    a[0] = header;
		//    int i = 1;
		//    foreach (GestureExpression expr in list)
		//    {
		//        a[i] = expr;
		//        i++;
		//    }
		//    return a;
		//}

		public static void SaveExpressions(ICollection<GestureExpression> list)
		{
			Persistence.SaveBinary("Expressions", Persistence.CreateArchive(list));

			Plugins.NotifySettingsChanged();
		}

		private static void EnumerateFreeTrails(Dictionary<string, GestureExpression> map, List<int> trails, List<int[]> dest, int current, int count)
		{
			if (trails.Count == count)
			{
				return;
			}

			if (current == trails.Count)
			{
				trails.Add(0);
			}

			for (int j = 0; j < 4; j++)
			{
				trails[current] = j;

				if (current >= 1 && trails[current] == trails[current - 1])
				{
					continue;	// 軌跡の重複を排除
				}

				int[] a = trails.ToArray();
				if (!map.ContainsKey(GestureExpression.FormatTrails(a)))
				{
					dest.Add(a);
				}

				EnumerateFreeTrails(map, new List<int>(trails), dest, current + 1, count);
			}
		}

		public static int[][] EnumerateFreeTrails(ICollection<GestureExpression> list, int count)
		{
			//NOTE: System.Array は Equals も GetHashCode も実装されてないので…
			Dictionary<string, GestureExpression> map = new Dictionary<string, GestureExpression>();
			foreach (GestureExpression expr in list)
			{
				if (!expr.TrailsEquals(GestureExpression.EmptyTrails))
				{
					map.Add(expr.FormatTrails(), expr);
				}
			}

			List<int> trails = new List<int>();
			List<int[]> dest = new List<int[]>();

			EnumerateFreeTrails(map, trails, dest, 0, count);

			//NOTE: 条件 0 より小さい。 x が y より小さい。0 x と y は等しい。0 より大きい。 x が y より大きい値です。
			dest.Sort(delegate(int[] t1, int[] t2)
			{
				int n = t1.Length - t2.Length;
				if (n != 0)
				{
					return n;
				}
				for (int i = 0; i < t1.Length; i++)	// 長さが同じ場合
				{
					int u = t1[i] - t2[i];
					if (u != 0)
					{
						return u;
					}
				}
				return 0;	// 等しい
			});

			return dest.ToArray();
		}
	}
}
